package prefix.transaction;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

import prefix.common.SystemActor;
import prefix.exception.TransactionException;
import prefix.exception.TransactionException.CODE;
import prefix.integration.wm.AbstractWalletManager.ExecutionStatus;
import prefix.integration.wm.AbstractWalletManager.WMStatus;
import prefix.integration.wm.WMException;
import prefix.util.JPAUtil;
import repository.OrderRepository;
import models.Loan;
import models.OrderDetail;
import models.Transaction;
import models.WmTransaction;
import models.Transaction.Type;
import models.WmTransaction.Status;

public class ThirdPartyFactory extends TransactionFactory {

    public static enum ThirdPartyStatus {
        PROCESSING, // 0 - processing
        EXECUTED, // 1 - successful
        FAILED, // 2 Failed in payment company
        UNKNOWN, // 3 Unknown state in payment company
    }

    abstract class ThirdParty extends BasicTransaction {
        protected ThirdPartyStatus getThirdPartyStatus() {
            return ThirdPartyStatus.EXECUTED;
        }
    }

    public class T0_AddFunds extends ThirdParty {

        public T0_AddFunds() {

        };

        @Override
        Type[] getAcceptedTypes() {
            // TODO Auto-generated method stub
            return new Type[] { Type.T0_ADD_FUNDS };
        }

        @Override
        void validateAmount() throws TransactionException {
        }

        public T0_AddFunds(Transaction trans) {
            this.dbTrans = trans;
        }

        public @Override
        ExecutionStatus executeTransaction() throws TransactionException {
            try {
                ThirdPartyStatus status = this.getThirdPartyStatus();
                switch (status) {
                case EXECUTED:
                    break;
                case PROCESSING:
                    throw new TransactionException(CODE.THIRD_STATUS_STILL_PENDING);
                case FAILED:
                case UNKNOWN:
                    throw new TransactionException(CODE.INVALID_3RD_STATUS);
                default:
                    throw new TransactionException(CODE.INVALID_3RD_STATUS);
                }
                ExecutionStatus exeStatus = manager.executeAddFunds(this.dbTrans.getFromAid(), this.dbTrans.getToAid(),
                        new BigDecimal(dbTrans.getAmount()));
                return exeStatus;
            } catch (TransactionException e) {
                throw e;
            } catch (Exception e) {
                e.printStackTrace();
                throw new TransactionException(CODE.ERROR_IN_EXECUTE, e);
            }
        }

        @Override
        Integer executeCreateTransaction() throws TransactionException {
            throw new TransactionException(CODE.NO_NEED_CREATION);
        }

        @Override
        ExecutionStatus executeRollbackTransaction() throws TransactionException {
            this.dbTrans.setStatus(Transaction.Status.FAILED);
            this.dbTrans.setUpdateD(new Date());
            throw new TransactionException(CODE.NO_NEED_FAIL);
        }

        @Override
        void validateWallet() throws TransactionException {
            if (!SystemActor.CUSTOMER_FUNDS_WALLET_W6.getId().equals(this.dbTrans.getFromAid()))
                throw new TransactionException(CODE.INVALID_WALLET);
        }

    }

    public class T1_LenderWithdrawl extends ThirdParty {
        public T1_LenderWithdrawl() {
            // TODO Auto-generated constructor stub
        }

        public T1_LenderWithdrawl(Transaction trans) {
            this.dbTrans = trans;
        }

        @Override
        Type[] getAcceptedTypes() {
            // TODO Auto-generated method stub
            return new Type[] { Type.T1_WITHDRAWL };
        }

        @Override
        Integer executeCreateTransaction() throws TransactionException {
            try {
                return manager.createWithdrawl(this.dbTrans.getFromAid(), this.dbTrans.getToAid(),
                        BigDecimal.valueOf(this.dbTrans.getAmount()));
            } catch (WMException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                throw new TransactionException(CODE.ERROR_IN_WM, e);
            }
        }

        @Override
        ExecutionStatus executeRollbackTransaction() throws TransactionException {
            // TODO Auto-generated method stub
            try {
                return manager.rollbackWithdrawl(Integer.valueOf(this.dbTrans.getExtId()));
            } catch (WMException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                throw new TransactionException(CODE.ERROR_IN_WM, e);
            }
        }

        @Override
        ExecutionStatus executeTransaction() throws TransactionException {
            try {
                ThirdPartyStatus status = this.getThirdPartyStatus();
                switch (status) {
                case EXECUTED:
                    break;
                case PROCESSING:
                    throw new TransactionException(CODE.THIRD_STATUS_STILL_PENDING);
                case FAILED:
                case UNKNOWN:
                    throw new TransactionException(CODE.INVALID_3RD_STATUS);
                default:
                    throw new TransactionException(CODE.INVALID_3RD_STATUS);
                }
                return manager.executeWithdrawFunds(Integer.valueOf(this.dbTrans.getExtId()));
            } catch (TransactionException e) {
                throw e;
            } catch (Exception e) {
                e.printStackTrace();
                throw new TransactionException(CODE.ERROR_IN_EXECUTE, e);
            }
        }

        @Override
        void validateAmount() throws TransactionException {
            BigDecimal balance = manager.getBalance(this.dbTrans.getFromAid());
            if (balance.doubleValue() < this.dbTrans.getAmount())
                throw new TransactionException(CODE.NOT_ENOUGH_MONEY);
        }

        @Override
        void validateAmountOnFail() throws TransactionException {
            BigDecimal balance = manager.getBalancePendingWithdrawl(this.dbTrans.getFromAid());
            if (balance.doubleValue() < this.dbTrans.getAmount())
                throw new TransactionException(CODE.NOT_ENOUGH_MONEY);
        }

        @Override
        void validateWallet() throws TransactionException {
            if (!SystemActor.CUSTOMER_WITHDRAW_WALLET_W5.getId().equals(this.dbTrans.getToAid()))
                throw new TransactionException(CODE.INVALID_WALLET);
        }
    }

    public class T3_PUSH_FUNDS_TO_BORROWER_BANK extends ThirdParty {
        public T3_PUSH_FUNDS_TO_BORROWER_BANK() {
            // TODO Auto-generated constructor stub
        };

        public T3_PUSH_FUNDS_TO_BORROWER_BANK(Transaction trans) {
            this.dbTrans = trans;
        }

        @Override
        Integer executeCreateTransaction() throws TransactionException {
            throw new TransactionException(CODE.NO_NEED_CREATION);
        }

        @Override
        ExecutionStatus executeRollbackTransaction() throws TransactionException {
            // TODO Auto-generated method stub
            throw new TransactionException(CODE.NO_NEED_FAIL);
        }

        @Override
        ExecutionStatus executeTransaction() throws TransactionException {
            // TODO Auto-generated method stub
            try {
                ThirdPartyStatus status = this.getThirdPartyStatus();
                switch (status) {
                case EXECUTED:
                    break;
                case PROCESSING:
                    throw new TransactionException(CODE.THIRD_STATUS_STILL_PENDING);
                case FAILED:
                case UNKNOWN:
                    throw new TransactionException(CODE.INVALID_3RD_STATUS);
                default:
                    throw new TransactionException(CODE.INVALID_3RD_STATUS);
                }

                return manager.pushFundsToBank(this.dbTrans.getFromAid(), this.dbTrans.getToAid(), new BigDecimal(
                        dbTrans.getAmount()));
            } catch (Exception e) {
                throw new TransactionException(CODE.ERROR_IN_WM, e);
            }
        }

        @Override
        void afterExecuteTransaction() throws TransactionException {
            Loan loan = JPAUtil.em().find(Loan.class, dbTrans.getLoanId());
            loan.setStatusD(new Date());
            loan.setStatus(Loan.Status.ISSUED);
            loan.setIssueD(new Date());
            List<OrderDetail> detailList = OrderRepository.getOrderDetailByLoanAndStatus(loan.getId(),
                    OrderDetail.Status.PENDING);
            for (OrderDetail detail : detailList) {
                detail.setStatus(OrderDetail.Status.ISSUED);
                detail.setStatusD(new Date());
            }
        }

        @Override
        Type[] getAcceptedTypes() {
            return new Type[] { Type.T3_PUSH_FUNDS_TO_BORROWER_BANK };
        }

        @Override
        void validateAmount() throws TransactionException {
            BigDecimal balance = manager.getBalance(this.dbTrans.getFromAid());
            if (balance.doubleValue() - this.dbTrans.getAmount() < 0)
                throw new TransactionException(CODE.NOT_ENOUGH_MONEY);
        }

        @Override
        void validateWallet() throws TransactionException {
            if (!this.dbTrans.getToAid().equals(SystemActor.PUSH_FUNDS_WALLET_W8.getId()))
                throw new TransactionException(CODE.INVALID_WALLET);
        }
    }

}
